/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019.
 * Description: dts reg operation language support
 * Author: xiaowei
 * Create: 2019-07-18
 */

#include <linux/hal/drol.h>
#include <linux/libfdt.h>

static void drol_getprop_u32(unsigned int *prop, const void *dtb_addr, const char *name, int offset)
{
	int size;
	const unsigned int *data = fdt_getprop(dtb_addr, offset, name, &size);

	*prop = 0;
	if (data)
		*prop = fdt32_to_cpu(*data);
}

static int drol_getprop_reg(phys_addr_t *prop, const void *dtb_addr, int offset, int addr_cells)
{
	int size = 0;
	const void *data = fdt_getprop(dtb_addr, offset, "reg", &size);

	if (!data)
		return -ENODEV;

	if (addr_cells == 2)
		*prop = (phys_addr_t)fdt64_to_cpu(*(unsigned long long *)data);
	else
		*prop = (phys_addr_t)fdt32_to_cpu(*(unsigned int *)data);
	return 0;
}

static void drol_getprop_op(unsigned int *prop, const void *dtb_addr, int offset)
{
	int size;
	const char *data = fdt_getprop(dtb_addr, offset, "op", &size);

	*prop = DROL_OP_READ | DROL_OP_WRITE;
	if (data) {
		if (!strcmp("readonly", data))
			*prop = DROL_OP_READ;
		else if (!strcmp("writeonly", data))
			*prop = DROL_OP_WRITE;
	}
}

static int get_addr_cells(unsigned int *addr_cells, const void *dtb_addr, int offset)
{
	int size;
	const unsigned int *data = NULL;
	int parent = offset;

	if (*addr_cells != 0)
		return 0;

	while (1) {
		parent = fdt_parent_offset(dtb_addr, parent);
		if (parent < 0)
			return -EINVAL;

		data = fdt_getprop(dtb_addr, parent, "#address-cells", &size);
		if (data) {
			*addr_cells = fdt32_to_cpu(*data);
			if (!addr_cells_range_ok(*addr_cells))
				return -EINVAL;
			return 0;
		}
	}
}

int drol_get_op_fdt(struct drol_op *op, int op_size, const char *name, const void *dtb_addr)
{
	int err;
	int offset;
	int addr_cells = 0;
	int depth = 0;
	int i = 0;

	if (!op || !name || !dtb_addr)
		return -EINVAL;

	offset = fdt_node_offset_by_compatible(dtb_addr, -1, name);
	if (offset < 0)
		return -ENODEV;

	while (1) {
		offset = fdt_next_node(dtb_addr, offset, &depth);
		if (offset < 0 || depth <= 0)
			return i;

		err = get_addr_cells(&addr_cells, dtb_addr, offset);
		if (err)
			return err;

		err = drol_getprop_reg(&op[i].reg, dtb_addr, offset, addr_cells);
		if (err)
			return err;

		drol_getprop_op(&op[i].op, dtb_addr, offset);
		drol_getprop_u32(&op[i].set, dtb_addr, "set", offset);
		drol_getprop_u32(&op[i].clear, dtb_addr, "clear", offset);
		drol_getprop_u32(&op[i].xor, dtb_addr, "xor", offset);
		drol_getprop_u32(&op[i].delay, dtb_addr, "delay", offset);

		i++;
		if (i >= op_size)
			return -ENOMEM;
	}
}
